
#include "../iface/LibSSH2Interface.h"
#include "../aprtools/APRAutoLock.h"
#include "ShellChannel.h"
#include "../utils/StringUtils.h"

/** Constructor for the shell channel.
 * 
 * @param itclient The SSH client object to use to create the shell
 * channel. */
ShellChannel::ShellChannel(SSHClient &itclient) : SSHChannel(itclient)
{
}

/** Destructor for the shell channel. */
ShellChannel::~ShellChannel()
{
}

LIBSSH2_CHANNEL *ShellChannel::initialiseShellChannel()
{
   LIBSSH2_CHANNEL *tchannel = NULL;
   if (tclient->isUp())
   {
      APRAutoLock autolock(tclient->getMutex());
      tchannel = libssh2_channel_open_session(const_cast<LIBSSH2_SESSION *>(tclient->getSSHSession()));
      if (tchannel == NULL)
         elCheckLibSSH2Error(tclient);
   } else
   {
      errprint("ShellChannel can not be established - SSH client not connected");
   }
   return tchannel;
}

/** Runs a command whose output goes into a bucket brigade which is
 * returned.
 * 
 * @param tchannel SSH channel to use.
 * 
 * @param command The command to run on the remote shell.
 * 
 * @return The bucket brigade containing the output of the command. */
APRBucketBrigade *ShellChannel::runCommandToBucket(LIBSSH2_CHANNEL *tchannel, const char *command)
{
   if (tclient == NULL) { errprint("no client - cannot run command"); return NULL; }
   APRBucketBrigade *newbb = new APRBucketBrigade(imp);//tclient->getMemoryPool());
   int retval = 0;
   {
      APRAutoLock autolock(tclient->getMutex());
      retval = libssh2_channel_exec(tchannel, command);
   }
   eCheckLibSSH2Error(tclient, retval);
   if (retval != 0)
   {
      char *errmsg;
      int errlen;
      int err = 0;
      {
         APRAutoLock autolock(tclient->getMutex());
         err = libssh2_session_last_error(const_cast<LIBSSH2_SESSION *>(tclient->getSSHSession()), &errmsg, &errlen, 0);
      }
      errprint("extended ssh error: %s", errmsg);
      delete newbb;
      return NULL;
   }
   int rc = 1;
   int timeout = 1;
   char *readbuf = (char *)malloc(sizeof(char)*SHELL_CMDBUFFER);
   while (rc > 0)
   {
      {
         APRAutoLock autolock(tclient->getMutex());
         rc = libssh2_channel_read(tchannel, readbuf, SHELL_CMDBUFFER);
      }
      if (rc > 0)
      {
         int byteswritten = newbb->writeToStream(readbuf, rc);
         if (byteswritten != rc)
         {
            errprint("could not push command output to bucket brigade");
            break;
         }
         timeout = 1;
      } else if (rc == LIBSSH2_ERROR_EAGAIN)
      {
         timeout <<= 1;
         timeout = (timeout > SSH_TIMEOUT_EAGAIN) ? SSH_TIMEOUT_EAGAIN : timeout;
         apr_sleep(timeout);
         rc = 1;
      } else if (rc < 0)
      {
         APRAutoLock autolock(tclient->getMutex());
         eCheckLibSSH2Error(tclient, retval);
      }
   }
   free(readbuf);
   // close and reopen channel - can't reuse channel for multiple
   // exec's
   return newbb;
}

/** Runs a command whose output goes into a string which is returned.
 * 
 * @param tchannel SSH channel to use.
 * 
 * @param command The command to run on the remote shell.
 * 
 * @return The string containing the output of the command. */
char *ShellChannel::runCommandToString(LIBSSH2_CHANNEL *tchannel, const char *command)
{
   if (tclient == NULL) { errprint("no client - cannot run command"); return NULL; }
   APRBucketBrigade *aprbb = runCommandToBucket(tchannel, command);
   const char *data = ""; 
   aprbb->writeToStream(data, 1);
   char *buffer = NULL;
   size_t length = 0;
   int bucketbrigadeget = aprbb->bucketBrigadeToBuffer(&buffer, &length);
   delete aprbb;
   return buffer;
}

/** Runs a command whose future output goes into a bucket brigade
 * which can be returned by the future when it is ready.
 * 
 * @param command The command to run on the remote shell.
 * 
 * @return The future which can return the bucket brigade when it is
 * ready. */
APRFuture<APRBucketBrigade *> *ShellChannel::runCommandToBucketFuture(const char *command)
{
   if (tclient == NULL) { errprint("no client - cannot run command"); return NULL; }
   LIBSSH2_CHANNEL *tchannel = initialiseShellChannel();
   if (tchannel == NULL)
   {
      errprint("channel failed - run command has no future");
      return NULL;
   }
   APRFuture<APRBucketBrigade *> *command_bucket_future = new APRFuture<APRBucketBrigade *>(tclient->getMemoryPool());
   command_bucket_future->addArgument((void *)this);
   command_bucket_future->addArgument(tchannel);
   command_bucket_future->addArgument(command);
   command_bucket_future->addFunctor(getRunCommandToBucketThread());
   command_bucket_future->addDestructor(stopChannelFuture);
   command_bucket_future->start();
   return command_bucket_future;
}

/** Runs a command whose future output goes into a string which can be
 * returned by the future when it is ready. The command is defined
 * variadically.
 * 
 * @param command The command to run on the remote shell (format
 * string).
 * 
 * @return The future which can return the string when it is ready. */
APRFuture<char *> *ShellChannel::runCommandToStringFuture(const char *command, ...)
{
   va_list args;
   va_list newcopy;
   va_start(args, command); va_copy(newcopy, args);
   char *tinput = StringUtils::varprintf_va(command, newcopy);
   va_end(newcopy); va_end(args);
   errprint("REMOTE CMD: %s", tinput);
   APRFuture<char *> *tret = runCommandToStringFuture_simple(tinput);
   free(tinput);
   return tret;
}

/** Runs a command whose future output goes into a string which can be
 * returned by the future when it is ready.
 * 
 * @param command The command to run on the remote shell.
 * 
 * @return The future which can return the string when it is ready. */
APRFuture<char *> *ShellChannel::runCommandToStringFuture_simple(const char *command)
{
   if (tclient == NULL) { errprint("no client - cannot run command"); return NULL; }
   LIBSSH2_CHANNEL *tchannel = initialiseShellChannel();
   if (tchannel == NULL)
   {
      errprint("channel failed - run simple command has no future");
      return NULL;
   }
   APRFuture<char *> *command_string_future = new APRFuture<char *>(tclient->getMemoryPool());
   command_string_future->addArgument((void *)this);
   command_string_future->addArgument(tchannel);
   command_string_future->addArgument(command, strlen(command));
   command_string_future->addFunctor(getRunCommandToStringThread());
   command_string_future->addDestructor(stopChannelFuture);
   command_string_future->start();
   return command_string_future;
}

/** A thread accessible wrapper that runs a command whose output goes
 * into a bucket brigade.
 * 
 * @param tthread The current thread pointer.
 * 
 * @param args A void pointer containing encoded arguments.
 * 
 * @return A pointer containing the return type in such a way that it
 * can be deleted by a future later. */
void *ShellChannel::runCommandToBucketThread(apr_thread_t *tthread, void *args)
{
   ShellChannel *kthis = (ShellChannel *)((void **)args)[0];
   LIBSSH2_CHANNEL *tchannel = (LIBSSH2_CHANNEL *)((void **)args)[1];
   const char *command = (const char *)((void **)args)[2];
   APRBucketBrigade **nret = new (APRBucketBrigade *)();
   *nret = kthis->runCommandToBucket(tchannel, command);
   return nret;
}

/** A thread accessible wrapper that runs a command whose output goes
 * into a string.
 * 
 * @param tthread The current thread pointer.
 * 
 * @param args A void pointer containing encoded arguments.
 * 
 * @return A pointer containing the return type in such a way that it
 * can be deleted by a future later. */
void *ShellChannel::runCommandToStringThread(apr_thread_t *tthread, void *args)
{
   ShellChannel *kthis = (ShellChannel *)((void **)args)[0];
   LIBSSH2_CHANNEL *tchannel = (LIBSSH2_CHANNEL *)((void **)args)[1];
   const char *command = (const char *)((void **)args)[2];
   char **nret = new (char *)();
   *nret = kthis->runCommandToString(tchannel, command);
   return nret;
}
